package com.shang.core.base;

import lombok.Getter;

import javax.xml.bind.DatatypeConverter;

/**
 * 附加位，变长使用
 * <p>
 * Created by shangwei2009@hotmail.com on 2021/9/7 10:56
 */
public abstract class Meta {

    /**
     * 域名称
     */
    @Getter
    private final String fieldName;

    /**
     * 域描述
     */
    @Getter
    private String description = "";

    /**
     * 附加位字节数（由这个字节解析出域长度），变长时生效（即metaLength>0，不管valueLength）
     */
    @Getter
    private final Integer metaLength;

    /**
     * 域字节数，定长时生效（即metaLength==0），后续可作为变长时限制总长度的参数
     */
    @Getter
    private final Integer valueLength;

    /**
     * 域长度类型（valueLength代表字符or字节），对metaLength无影响
     */
    @Getter
    private final LengthType lengthType;

    protected Meta(String fieldName, Integer metaLength, Integer valueLength, LengthType lengthType) {
        if (fieldName == null) {
            throw new CodecException("域名称不能为空");
        }
        this.fieldName = fieldName;
        if ((metaLength == null) == (valueLength == null)) {
            throw new CodecException(fieldName + "要么定长，要么变长（metaLength和valueLength有且仅有一个不为空）");
        }
        if (metaLength != null && metaLength < 1) {
            // 变长则附加位长度必为正数
            throw new CodecException(fieldName + "的附加位长度必须为正数");
        }
        if (valueLength != null && valueLength < 0) {
            // 定长则域长度必为自然数
            throw new CodecException(fieldName + "的域长度不能为负数");
        }
        this.metaLength = metaLength;
        this.valueLength = valueLength;
        if (lengthType == null) {
            throw new CodecException(fieldName + "必须指定域长度类型（valueLength）");
        }
        this.lengthType = lengthType;
    }

    @SuppressWarnings("unchecked")
    public final <T extends Meta> T description(String description) {
        if (description != null) {
            this.description = description;
        }
        return (T) this;
    }

    public final Wrapper decode(String hexStr) {
        if (hexStr == null || hexStr.trim().isEmpty()) {
            throw new CodecException(fieldName + "的域为空");
        }
        if (hexStr.length() % 2 != 0) {
            throw new CodecException(fieldName + "的域长度错误");
        }
        try {
            DatatypeConverter.parseHexBinary(hexStr);
        } catch (IllegalArgumentException e) {
            throw new CodecException(fieldName + "遇到非Hex字符串");
        }
        // 变长
        if (metaLength != null) {
            try {
                final Wrapper wrapper = decodeInternal(hexStr, metaLength);
                final Integer valueLength = wrapper.getValueLength();
                if (valueLength == null) {
                    throw new CodecException(fieldName + "的域长度不能为空");
                }
                if (valueLength < 0) {
                    throw new CodecException(fieldName + "的域长度不能为负数");
                }
                return wrapper.fieldName(fieldName).description(description);
            } catch (StringIndexOutOfBoundsException e) {
                throw new CodecException(fieldName + "的附加位长度错误");
            } catch (NumberFormatException e) {
                throw new CodecException(fieldName + "的附加位解析错误");
            }
        }
        // 定长
        else if (valueLength != null) {
            return new Wrapper(valueLength, "", "", hexStr).fieldName(fieldName).description(description);
        } else {
            throw new CodecException(fieldName + "无法处理的Meta");
        }
    }

    /**
     * 附加位解析方法
     * <p>
     * PS: 也可以去掉{@link #valueLength(String)}方法，直接实现这个方法
     *
     * @param hexStr     当前剩余Hex串
     * @param metaLength 附加位字节数
     * @return {@link Wrapper}
     */
    private Wrapper decodeInternal(String hexStr, Integer metaLength) {
        // 使用自带substring顺带检验长度
        final String metaStr = hexStr.substring(0, metaLength * 2);
        final int vLength = valueLength(metaStr);
        return new Wrapper(vLength, metaStr, metaStr, hexStr.substring(metaLength * 2));
    }

    /**
     * 附加位Hex提取域长度方法
     *
     * @param metaStr 当前附加位Hex
     * @return 域长度（不包含附加位本身，这个长度根据实际情况来决定是字节数、字符数）
     */
    protected abstract Integer valueLength(String metaStr);
}
